<?php

namespace App\Trip\IdentityCode;

use App\Trip\DealStr\HidePartOfString;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;

class IdentityCode
{
    private string $identity_code;

    /**
     * 加权因子
     * @var string[]
     */
    private array $f18
        = [
            1  => "7", 2 => "9",
            3  => "10", 4 => "5",
            5  => "8", 6 => "4",
            7  => "2", 8 => "1",
            9  => "6", 10 => "3",
            11 => "7", 12 => "9",
            13 => "10", 14 => "5",
            15 => "8", 16 => "4",
            17 => "2",
        ];

    // 校验码
    /**
     * 校验码
     * @var string[]
     */
    private array $v18
        = [
            0  => "1",
            1  => "0",
            2  => "X",
            3  => "9",
            4  => "8",
            5  => "7",
            6  => "6",
            7  => "5",
            8  => "4",
            9  => "3",
            10 => "2",
        ];

    private array $sex_list
        = [
            0 => "未知",
            1 => "男",
            2 => "女",
        ];

    public function __construct(string $identity_code)
    {
        // 先转为大写
        $this->identity_code = Str::upper($identity_code);
    }

    // 获取证件号长度

    /**
     * 获取身份证号码
     * @throws Exception
     */
    public function getIdentityCode(): string
    {
        if (!$this->isValid()) {
            throw new Exception("身份证号码格式错误");
        }
        return $this->identity_code;
    }

    /**
     * 身份证格式是否有效
     * @return bool
     */
    public function isValid(): bool
    {
        $base = preg_match('/(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/', $this->identity_code);
        if (!$base) {
            return false;
        }

        // 15 位无法校验 只要生日格式对了就算有效
        if (!Carbon::hasFormat($this->getBirthDay(), 'Ymd')) {
            return false;
        }

        if (15 == $this->getLength()) {
            return true;
        }


        // 本体码乘以其加权因子并求和
        $sum = 0;
        for ($idx = 0; $idx < mb_strlen($this->identity_code) - 1; $idx++) {
            $num    = $this->identity_code[$idx];
            $factor = $this->f18[$idx + 1];
            $sum    += $num * $factor;
        }

        //和除以11取余数，得到校验码：
        $remainder    = $sum % count($this->v18);
        $valid_number = $this->v18[$remainder];

        if ($valid_number != $this->getValidCode()) {
            return false;
        }
        return true;
    }

    public function getBirthDay(): string
    {
        // 老证件号 7-12 位是生日
        if ($this->getLength() != 18) {
            return "19".Str::substr($this->identity_code, 6, 6);
        }
        // 7-14 位是生日
        return Str::substr($this->identity_code, 6, 8);
    }

    private function getLength(): int
    {
        return mb_strlen($this->identity_code);
    }

    /**
     * 获取身份证号的校验位
     * @return string
     * @throws Exception
     */
    private function getValidCode(): string
    {
        // 老证件号 无法获取
        if ($this->getLength() != 18) {
            throw new Exception("老证件号无校验码");
        }
        $validCode = Str::substr($this->identity_code, 17, 1);
        return Str::upper($validCode);
    }

    /**
     * 性别描述
     * @return void
     */
    public function getSexDesc()
    {
        return $this->sex_list[$this->getSex()] ?? $this->sex_list[0];
    }

    /**
     * 获取性别
     * @return int
     */
    public function getSex(): int
    {
        $isValid = $this->isValid();
        if (!$isValid) {
            return 0;
        }
        // 老证件号 无法获取
        if ($this->getLength() != 18) {
            return 0;
        }


        $sex = $this->getSexValue();
        if ($sex % 2 == 0) {
            return 2; // 女
        }
        return 1; // 男
    }

    private function getSexValue(): string
    {
        return Str::substr($this->identity_code, 16, 1);
    }

    public function hide($start = 6, $end = 2): string
    {
        return (new HidePartOfString($this->identity_code))->hide($start, $end);
//        $count = $this->getLength() - $start - $end;
//        $str   = str_repeat("*", $count);
//        return Str::substrReplace($this->identity_code, $str, $start, $this->getLength() - $start - $end);
    }
}